//  Copyright (c) 2010 Mahesh Sharma,Matt MacDonald
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.


package org.prx.prp;

import java.io.IOException;
import java.lang.ref.WeakReference;

import org.prx.prp.IRockOnNextGenService;
import org.prx.prp.R;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.PowerManager.WakeLock;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;

public class BackgroundPlaybackService extends Service {
	private static final String TAG = "PRPAND";
	public static final int PLAYBACKSERVICE_STATUS = 1;
	private static final int EPISODE_ENDED = 1;
	private static final int RELEASE_WAKELOCK = 2;
	private static final int SERVER_DIED = 3;
	private static final int FADEIN = 4;
	public static int STOP_SERVICE=5;
	private MultiPlayer mPlayer;
	final Service s;
	private String URLToPlay;
	private WakeLock mWakeLock;
	private int mServiceStartId = -1;
	private boolean mServiceInUse = false;
	private boolean resumeAfterCall = false;	
	private static final int IDLE_DELAY = 60000;
	private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			
			if (state == TelephonyManager.CALL_STATE_RINGING) {
				AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
				int ringvolume = audioManager
						.getStreamVolume(AudioManager.STREAM_RING);
				if (ringvolume > 0) {
					resumeAfterCall = (isPlaying() || resumeAfterCall);
					pause();
				}
			} else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
				resumeAfterCall = (isPlaying() || resumeAfterCall);
				pause();
			} else if (state == TelephonyManager.CALL_STATE_IDLE) {
				if (resumeAfterCall) {
					startAndFadeIn();
					resumeAfterCall = false;
				}
			}
		}
	};

	private void startAndFadeIn() {
		mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
	}

	private Handler mMediaplayerHandler = new Handler() {
		float mCurrentVolume = 1.0f;
	

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case FADEIN:
				if (!isPlaying()) {
					mCurrentVolume = 0f;
					mPlayer.setVolume(mCurrentVolume);
					play();
					mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
				} else {
					mCurrentVolume += 0.01f;
					if (mCurrentVolume < 1.0f) {
						mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
					} else {
						mCurrentVolume = 1.0f;
					}
					mPlayer.setVolume(mCurrentVolume);
				}
				break;
			case EPISODE_ENDED:
				notifyChange(Constants.PLAYBACK_COMPLETE);				
				break;
			case RELEASE_WAKELOCK:
				mWakeLock.release();
				break;			
			default:
				break;
			}
		}		
	};

	private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			if (Constants.TOGGLEPAUSE_ACTION.equals(action)) {
				if (isPlaying()) {
					pause();
				} else {
					play();
				}
			}
			else if (Constants.STOP_SERVICE.equals(action))
			{
				onDestroy();
			}
			notifyChange(Constants.PLAYSTATE_CHANGED);
		}
	};

	public BackgroundPlaybackService() {
		s=this;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		Log.i(TAG, "1SERVICE onCreate");
		mPlayer = new MultiPlayer();
		mPlayer.setHandler(mMediaplayerHandler);
		IntentFilter commandFilter = new IntentFilter();
		commandFilter.addAction(Constants.TOGGLEPAUSE_ACTION);
		commandFilter.addAction(Constants.STOP_SERVICE);
		registerReceiver(mIntentReceiver, commandFilter);
		TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
		tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
		mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this
				.getClass().getName());
		mWakeLock.setReferenceCounted(false);
		Message msg = mDelayedStopHandler.obtainMessage();
		mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
	}

	@Override
	public void onDestroy() {

		// Check that we're not being destroyed while something is still
		// playing.
		if (isPlaying()) {
			Log.i(TAG, "Service being destroyed while still playing.");
		}
		// release all MediaPlayer resources, including the native player and
		// wakelocks
		mPlayer.release();
		mPlayer = null;

		// make sure there aren't any other messages coming
		mDelayedStopHandler.removeCallbacksAndMessages(null);
		mMediaplayerHandler.removeCallbacksAndMessages(null);
		TelephonyManager tmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
		tmgr.listen(mPhoneStateListener, 0);

		unregisterReceiver(mIntentReceiver);
		
		mWakeLock.release();
		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancel(Constants.PLAY_NOTIFICATION_ID);
		//stopForeground(true);
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent intent) {
		Log.d("PRPAND", "In Bind");
		mDelayedStopHandler.removeCallbacksAndMessages(null);
		mServiceInUse = true;
		return mBinder;
	}

	@Override
	public void onRebind(Intent intent) {
		mDelayedStopHandler.removeCallbacksAndMessages(null);
		mServiceInUse = true;
	}

	@Override
	public void onStart(Intent intent, int startId) {
		this.onStartCommand(intent, 0, startId);
	}

	public int onStartCommand(Intent intent, int flags, int startId) {
		mServiceStartId = startId;

		mDelayedStopHandler.removeCallbacksAndMessages(null);

		Log.i(TAG, "Receiving intent in service");

		if (intent != null) {
			String action = intent.getAction();
			String cmd = intent.getStringExtra(Constants.CMDNAME);

			Log.i(TAG, "Receiving start command: " + cmd);

			if (Constants.CMDTOGGLEPAUSE.equals(cmd)
					|| Constants.TOGGLEPAUSE_ACTION.equals(action)) {
				if (isPlaying()) {
					pause();
				} else {
					play();
				}
			}
		}

		// make sure the service will shut down on its own if it was
		// just started but not bound to and nothing is playing
		mDelayedStopHandler.removeCallbacksAndMessages(null);
		Message msg = mDelayedStopHandler.obtainMessage();
		mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
		return Constants.START_STICKY;
	}

	@Override
	public boolean onUnbind(Intent intent) {
		mServiceInUse = false;
		if (isPlaying() || resumeAfterCall) {
			// something is currently playing, or will be playing once
			// an in-progress call ends, so don't stop the service now.
			return true;
		}

		// If there is a playlist but playback is paused, then wait a while
		// before stopping the service, so that pause/resume isn't slow.
		// Also delay stopping the service if we're transitioning between
		// tracks.
		if (mMediaplayerHandler.hasMessages(EPISODE_ENDED)) {
			Message msg = mDelayedStopHandler.obtainMessage();
			mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
			return true;
		}

		// No active playlist, OK to stop the service right now
		setForeground(true);
		stopSelf(mServiceStartId);
		return true;
	}

	private Handler mDelayedStopHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			// Check again to make sure nothing is playing right now
			if (isPlaying() || resumeAfterCall || mServiceInUse
					|| mMediaplayerHandler.hasMessages(EPISODE_ENDED)) {
				return;
			}
			setForeground(false);
			stopSelf(mServiceStartId);
		}
	};

	private String programTitle;
	private String stationName;
	public String getStationName() {
		return stationName;
	}

	public void setStationName(String stationName) {
		this.stationName = stationName;
	}

	private int programID;

	/**
	 * Notify the change-receivers that something has changed. The intent that
	 * is sent contains the following data for the currently playing track: "id"
	 * - Integer: the database row ID "artist" - String: the name of the artist
	 * "album" - String: the name of the album "track" - String: the name of the
	 * track The intent has an action that is one of
	 * "com.android.music.metachanged" "com.android.music.queuechanged",
	 * "com.android.music.playbackcomplete" "com.android.music.playstatechanged"
	 * respectively indicating that a new track has started playing, that the
	 * playback queue has changed, that playback has stopped because the last
	 * file in the list has been played, or that the play-state changed
	 * (paused/resumed).
	 */
	private void notifyChange(String what) {
		Intent i = new Intent(what);
		i.putExtra("PROGRAM_ID", getProgramID());
		i.putExtra("PROGRAM_NAME", getProgramTitle());
		i.putExtra("STATION_NAME",getStationName());
		sendBroadcast(i);
	}

	public String getURLToPlay() {
		return URLToPlay;
	}

	public void setURLToPlay(String uRLToPlay) {
		URLToPlay = uRLToPlay;
	}

	public String getProgramTitle() {
		return programTitle;
	}

	public void setProgramTitle(String programTitle) {
		this.programTitle = programTitle;
	}

	public int getProgramID() {
		return programID;
	}

	public void setProgramID(int programID) {
		this.programID = programID;
	}

	public void openAsync(String name, String stationName, String programURL, int programID) {
		synchronized (this) {
			if (programURL == null) {
				return;
			}

			URLToPlay = programURL;
			this.programTitle = name;
			this.programID = programID;
			this.stationName=stationName;
			mPlayer.open(URLToPlay);
		}
	}

	public void open(String programTitle,String stationName, String programURL, int programID) {
		synchronized (this) {
			if (programURL == null) {
				return;
			}

			URLToPlay = programURL;
			this.programTitle = programTitle;
			this.programID = programID;
			this.stationName=stationName;
			setNotification(R.drawable.play,programTitle);
			mPlayer.open(URLToPlay);
			notifyChange(Constants.PLAYSTATE_CHANGED);
		}
	}

	/**
	 * Starts playback of a previously opened file.
	 */
	public void setNotification(int iconDrawable,String text)
	{
		int icon = iconDrawable;
		long when = System.currentTimeMillis();
		Notification notification = new Notification(icon, text, when);
		notification.flags = Notification.FLAG_NO_CLEAR
				| Notification.FLAG_ONGOING_EVENT;
		Context c = getApplicationContext();
		CharSequence title = getString(R.string.app_name);
		//Intent notificationIntent = new Intent(this,PRX.class);
		//notificationIntent.setAction(Intent.ACTION_VIEW);
		//notificationIntent.addCategory(Intent.CATEGORY_DEFAULT);
		//notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

		PendingIntent contentIntent = PendingIntent.getActivity(c, 0,
				null, PendingIntent.FLAG_CANCEL_CURRENT);
		notification.setLatestEventInfo(c, title, text, contentIntent);
		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager
				.notify(Constants.PLAY_NOTIFICATION_ID, notification);

	}
	public void play() {
		if (!mPlayer.isInitialized())
			return;
		mPlayer.play();
		setNotification(R.drawable.play,programTitle);
	}

	private void stop(boolean remove_status_icon) {
		if (mPlayer.isInitialized()) {
			mPlayer.stop();
			//TODO: Remove notification
		}
		URLToPlay = null;
		

	}

	/**
	 * Stops playback.
	 */
	public void stop() {
		stop(true);
	}

	/**
	 * Pauses playback (call play() to resume)
	 */
	public void pause() {
		synchronized (this) {
			if (isPlaying()) {
				mPlayer.pause();
				gotoIdleState();				
				notifyChange(Constants.PLAYSTATE_CHANGED);
				setNotification(R.drawable.pause_button_control,programTitle);
			}
		}
	}

	/**
	 * Returns whether something is currently playing
	 * 
	 * @return true if something is playing (or will be playing shortly, in case
	 *         we're currently transitioning between tracks), false if not.
	 */
	public boolean isPlaying() {
		return mPlayer.isPlaying();
	}

	private void gotoIdleState() {
		mDelayedStopHandler.removeCallbacksAndMessages(null);
		Message msg = mDelayedStopHandler.obtainMessage();
		mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancel(Constants.PLAY_NOTIFICATION_ID);
		// stopForeground(true);
	}

	/**
	 * Returns the path of the currently playing file, or null if no file is
	 * currently playing.
	 */
	public String getPath() {
		return URLToPlay;
	}

	/**
	 * Returns the duration of the file in milliseconds. Currently this method
	 * returns -1 for the duration of MIDI files.
	 */
	public long duration() {
		if (mPlayer.isInitialized()) {
			return mPlayer.duration();
		}
		return -1;
	}

	/**
	 * Returns the current playback position in milliseconds
	 */
	public long position() {
		if (mPlayer.isInitialized()) {
			return mPlayer.position();
		}
		return -1;
	}

	/**
	 * Seeks to the position specified.
	 * 
	 * @param pos
	 *            The position to seek to, in milliseconds
	 */
	public long seek(long pos) {
		if (mPlayer.isInitialized()) {
			if (pos < 0)
				pos = 0;
			if (pos > mPlayer.duration())
				pos = mPlayer.duration();
			return mPlayer.seek(pos);
		}
		return -1;
	}

	private class MultiPlayer {
		private MediaPlayer mMediaPlayer = new MediaPlayer();
		private Handler mHandler;

		public MultiPlayer() {
			mMediaPlayer.setWakeMode(BackgroundPlaybackService.this,
					PowerManager.PARTIAL_WAKE_LOCK);
		}

		public boolean isPlaying() {
			return mMediaPlayer.isPlaying();
		}

		public void open(String uRLToPlay) {			
				listen(uRLToPlay);			
		}

		public boolean isInitialized() {
			return isPrepared;
		}

		public Handler mStartHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				mMediaPlayer.start();
			}
		};

		public void stop() {
			mStartHandler.removeCallbacksAndMessages(null);
			mMediaPlayer.reset();
			isPrepared = false;
		}

		/**
		 * You CANNOT use this player anymore after calling release()
		 */
		public void release() {
			stop();
			mMediaPlayer.release();
			mMediaPlayer = null;
		}

		public void pause() {
			mMediaPlayer.pause();
		}

		public void setHandler(Handler handler) {
			mHandler = handler;
		}

		MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
			public void onCompletion(MediaPlayer mp) {
				mWakeLock.acquire(10000);
				mHandler.sendEmptyMessage(EPISODE_ENDED);
				mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
			}
		};

		MediaPlayer.OnPreparedListener preparedlistener = new MediaPlayer.OnPreparedListener() {
			public void onPrepared(MediaPlayer mp) {
				notifyChange(Constants.ASYNC_OPEN_COMPLETE);
				synchronized (this) {
					if (mMediaPlayer != null) {
						isPrepared = true;
					}
				}
				play();
			}
		};

		MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
			public boolean onError(MediaPlayer mp, int what, int extra) {

				switch (what) {
				case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
					isPrepared = false;
					mMediaPlayer.release();
					mMediaPlayer = new MediaPlayer();
					mMediaPlayer.setWakeMode(BackgroundPlaybackService.this,
							PowerManager.PARTIAL_WAKE_LOCK);
					mHandler.sendMessageDelayed(mHandler
							.obtainMessage(SERVER_DIED), 2000);
					Log.i(TAG, "MEDIA SERVER ERROR");
					return true;
				default:
					break;
				}
				return false;
			}
		};
		private boolean isPrepared;
		private StreamProxy proxy;

		synchronized public void play() {
			if (!isPrepared) {
				return;
			}
			mMediaPlayer.start();
		}

		public long duration() {
			return mMediaPlayer.getDuration();
		}

		public long position() {
			return mMediaPlayer.getCurrentPosition();
		}

		public long seek(long whereto) {
			mMediaPlayer.seekTo((int) whereto);
			return whereto;
		}

		public void setVolume(float vol) {
			mMediaPlayer.setVolume(vol, vol);
		}

		public void listen(String url) {
			try {
					if(proxy==null)
					{
					proxy = new StreamProxy();
					proxy.init(mMediaPlayer,s);
					proxy.start();
					}
				String proxyUrl = String.format("http://127.0.0.1:%d/%s", proxy
						.getPort(), url);
				String playUrl = proxyUrl;
				mMediaPlayer.reset();
				mMediaPlayer.setDataSource(playUrl);
				/*try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/
				mMediaPlayer.prepareAsync();
				mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
					public void onPrepared(MediaPlayer arg0) {
						isPrepared=true;
						mMediaPlayer.start();
						notifyChange(Constants.PLAYSTATE_CHANGED);
					}
				});
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalStateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	/*
	 * By making this a static class with a WeakReference to the Service, we
	 * ensure that the Service can be GCd even when the system process still has
	 * a remote reference to the stub.
	 */
	static class ServiceStub extends IRockOnNextGenService.Stub {
		WeakReference<BackgroundPlaybackService> mService;

		ServiceStub(BackgroundPlaybackService service) {
			mService = new WeakReference<BackgroundPlaybackService>(service);
		}

		public boolean isPlaying() {
			return mService.get().isPlaying();
		}

		public void stop() {
			mService.get().stop();
		}

		public void pause() {
			mService.get().pause();
		}

		public void play() {
			mService.get().play();
		}

		public String getPath() {
			return mService.get().getPath();
		}

		public long position() {
			return mService.get().position();
		}

		public long duration() {
			return mService.get().duration();
		}

		public long seek(long pos) {
			return mService.get().seek(pos);
		}

		@Override
		public long getProgramID() throws RemoteException {
			return mService.get().getProgramID();
		}

		@Override
		public String getProgramTitle() throws RemoteException {
			return mService.get().getProgramTitle();
		}

		@Override
		public String getStationName() throws RemoteException {
			return mService.get().getStationName();
		}
		
		@Override
		public String getProgramURL() throws RemoteException {
			return mService.get().getURLToPlay();
		}

		@Override
		public void openFile(String programTitle,String stationName,String programURL,
				int programID) throws RemoteException {
			// TODO Auto-generated method stub
			mService.get().open(programTitle,stationName, programURL, programID);
		}

		@Override
		public void openFileAsync(String programTitle, String stationName,String programURL,
				int programID) throws RemoteException {
			// TODO Auto-generated method stub
			mService.get().openAsync(programTitle, stationName,programURL, programID);
		}
	}

	private final IBinder mBinder = (IBinder) new ServiceStub(this);
}

